package io.renren.utils;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.github.difflib.DiffUtils;
import com.github.difflib.UnifiedDiffUtils;
import com.github.difflib.patch.AbstractDelta;
import com.github.difflib.patch.DeltaType;
import com.github.difflib.patch.Patch;
import io.renren.config.MongoManager;
import io.renren.entity.ColumnEntity;
import io.renren.entity.TableEntity;
import io.renren.entity.mongo.MongoDefinition;
import io.renren.entity.mongo.MongoGeneratorEntity;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成器   工具类
 *
 * @author chenshun
 * @email sunlightcs@gmail.com
 * @date 2016年12月19日 下午11:40:24
 */
public class GenUtils {

    private static String currentTableName;

    public static List<String> getTemplates() {
        List<String> templates = new ArrayList<String>();
//        templates.add("template/Entity.java.vm");
        templates.add("template/Dao.xml.vm");
//        templates.add("template/Dao.java.vm");
//        templates.add("template/EntityDto.java.vm");
//        templates.add("template/Service.java.vm");
//        templates.add("template/Manager.java.vm");
//        templates.add("template/ManagerImpl.java.vm");
//        templates.add("template/EntityReq.java.vm");
//        templates.add("template/EntityResp.java.vm");
//        templates.add("template/EntityCreateReq.java.vm");
//        templates.add("template/EntityUpdateReq.java.vm");
//
//
//        templates.add("template/Controller.java.vm");
//        templates.add("template/Service_Portal.java.vm");
//        templates.add("template/EntityCreateReq_Portal.java.vm");
//        templates.add("template/EntityResp_Portal.java.vm");
//        templates.add("template/EntityUpdateReq_Portal.java.vm");
//        templates.add("template/EntityDto_Portal.java.vm");
//        templates.add("template/EntityReq_Portal.java.vm");
//        templates.add("template/index.html.vm");
        templates.add("template/menu.sql.vm");

        return templates;
    }

    public static List<String> getMongoChildTemplates() {
        List<String> templates = new ArrayList<String>();
        templates.add("template/MongoChildrenEntity.java.vm");
        return templates;
    }

    /**
     * 生成代码
     */
    public static void generatorCode(Map<String, String> table, List<Map<String, String>> columns,List<String> templates) {
        //配置信息
        Configuration config = getConfig();
        boolean hasBigDecimal = false;
        boolean hasLocalDateTime = false;
        boolean hasList = false;

        //path=D:/dev/workspace/idea/node/gs-wms/wms-service/src
        //apiPath=D:/dev/workspace/idea/node/gs-wms/wms-api/src
        //frontPath=D:/dev/workspace/idea/node/gs-portal/src
        //根据表名自动识别模块名称
        String modelName = table.get("tableName").split("_")[0];
        config.setProperty("moduleName",modelName);
        config.setProperty("tablePrefix",new String[]{modelName+"_"});
        String path = config.getString("path");
        String frontModuleName = config.getString("frontModuleName");
        String pathTmp = path + "/gs-MODEL_NAME/MODEL_NAME-service/src".replace("MODEL_NAME",modelName);
        String apiPath = path + "/gs-MODEL_NAME/MODEL_NAME-api/src".replace("MODEL_NAME",modelName);
        String frontPath = path + "/gs-FRONT_MODULE_NAME/src".replace("FRONT_MODULE_NAME",frontModuleName);
        config.setProperty("path",pathTmp);
        config.setProperty("apiPath",apiPath);
        config.setProperty("frontPath",frontPath);

        //表信息
        TableEntity tableEntity = new TableEntity();
        tableEntity.setTableName(table.get("tableName"));
        tableEntity.setComments(table.get("tableComment"));
        //表名转换成Java类名
        String classNameNo = tableToJava(tableEntity.getTableName(), config.getStringArray("tablePrefix"));
        String className = tableToJava(tableEntity.getTableName(), null);

        tableEntity.setClassName(className);
        tableEntity.setClassNameNo(classNameNo);
        tableEntity.setClassnameNo(StringUtils.uncapitalize(classNameNo));
        tableEntity.setClassname(StringUtils.uncapitalize(className));

        //列信息
        List<ColumnEntity> columsList = new ArrayList<>();
        for (Map<String, String> column : columns) {
            if ("yn".equals(column.get("columnName"))) {
                continue;
            }
            ColumnEntity columnEntity = new ColumnEntity();
            columnEntity.setColumnName(column.get("columnName"));
            columnEntity.setDataType(column.get("dataType"));
            columnEntity.setComments(column.get("columnComment"));
            columnEntity.setExtra(column.get("extra"));

            //列名转换成Java属性名
//            String attrName = columnToJava(columnEntity.getColumnName());
            String attrName = columnEntity.getColumnName();
            columnEntity.setAttrName(attrName.substring(0,1).toUpperCase(Locale.ROOT)+attrName.substring(1));
            columnEntity.setAttrname(attrName.substring(0,1).toLowerCase(Locale.ROOT)+attrName.substring(1));
            columnEntity.setAttr_name(StrUtil.toUnderlineCase(attrName));
            //列的数据类型，转换成Java类型
            String attrType = config.getString(columnEntity.getDataType(), columnToJava(columnEntity.getDataType()));
            columnEntity.setAttrType(attrType);

            String jdbcType = config.getString("jdbcType." + columnEntity.getDataType());
            columnEntity.setJdbcType(jdbcType);

            if (!hasBigDecimal && attrType.equals("BigDecimal")) {
                hasBigDecimal = true;
            }

            if (!hasLocalDateTime && attrType.equals("LocalDateTime")) {
                hasLocalDateTime = true;
            }

            if (!hasList && "array".equals(columnEntity.getExtra())) {
                hasList = true;
            }
            //是否主键
            if ("PRI".equalsIgnoreCase(column.get("columnKey")) && tableEntity.getPk() == null) {
                tableEntity.setPk(columnEntity);
            }

            columsList.add(columnEntity);
        }
        tableEntity.setColumns(columsList);

        //没主键，则第一个字段为主键
        if (tableEntity.getPk() == null) {
            tableEntity.setPk(tableEntity.getColumns().get(0));
        }

        //设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);
        String mainPath = config.getString("mainPath");
        mainPath = StringUtils.isBlank(mainPath) ? "io.renren" : mainPath;
        //封装模板数据
        Map<String, Object> map = new HashMap<>();
        map.put("tableName", tableEntity.getTableName());
        map.put("table_name", StrUtil.toUnderlineCase(tableEntity.getTableName()));
        map.put("comments", tableEntity.getComments());
        map.put("pk", tableEntity.getPk());
        map.put("className", tableEntity.getClassName());
        map.put("classname", tableEntity.getClassname());
        map.put("classNameNo", tableEntity.getClassNameNo());
        map.put("classnameNo", tableEntity.getClassnameNo());
        map.put("pathName", tableEntity.getClassname().toLowerCase());
        map.put("columns", tableEntity.getColumns());
        map.put("hasBigDecimal", hasBigDecimal);
        map.put("hasLocalDateTime", hasLocalDateTime);
        map.put("hasList", hasList);
        map.put("mainPath", mainPath);
        map.put("frontModuleName", config.getString("frontModuleName"));
        map.put("package", config.getString("package"));
        map.put("moduleName", config.getString("moduleName"));
        map.put("author", config.getString("author"));
        map.put("email", config.getString("email"));
        map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN));
        VelocityContext context = new VelocityContext(map);

        //获取模板列表
        for (String template : templates) {
            //渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);
            String fileName = getFilePath(template, tableEntity.getClassNameNo(), tableEntity.getClassnameNo(), tableEntity.getClassName(), config.getString("package"), config.getString("moduleName"));
            if (StringUtils.isBlank(fileName)) {
                continue;
            }
            try {
                File file = new File(fileName);
                File filePath = new File(file.getParent());
                if (!filePath.exists()) {
                    filePath.mkdirs();
                }
                if (file.exists()) {
                    //如果存在，则进行比较合并
                    List<String> original = Files.readAllLines(file.toPath());
                    List<String> target = List.of(sw.toString().split("\\n"));
                    target = target.stream().map(a -> {
                        return a.replaceAll("\\r", "");
                    }).collect(Collectors.toList());
                    //查询差异
                    Patch<String> diff = DiffUtils.diff(original, target);
                    Iterator<AbstractDelta<String>> iterator = diff.getDeltas().iterator();
                    boolean hasEdit = false;
                    while (iterator.hasNext()){
                        AbstractDelta<String> next = iterator.next();
                        if (!next.getType().equals(DeltaType.INSERT)) {
                            iterator.remove();
                            hasEdit = true;
                        }
                    }
                    //合并差异
                    List<String> result = DiffUtils.patch(original, diff);
                    //输出文件
                    FileOutputStream fileOutputStream = new FileOutputStream(file);
                    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
                    for (String line : result) {
                        bufferedOutputStream.write(line.concat("\n").getBytes());
                    }
                    bufferedOutputStream.close();
                    fileOutputStream.close();
                    if (hasEdit) {
                        //输出临时文件,方便比较
                        String substring = fileName.substring(0, fileName.indexOf("src")) + File.separator + "target/temp"+File.separator + file.getName();
                        File tempFile = new File(substring);
                        File tempFilePath = new File(tempFile.getParent());
                        if (!tempFilePath.exists()) {
                            tempFilePath.mkdirs();
                        }
                        FileOutputStream tempFileOutputStream = new FileOutputStream(substring);
                        IOUtils.write(sw.toString(), tempFileOutputStream, "UTF-8");
                        IOUtils.closeQuietly(sw);
                        tempFileOutputStream.close();
                    }
                }else {
                    FileOutputStream fileOutputStream = new FileOutputStream(file);
                    IOUtils.write(sw.toString(), fileOutputStream, "UTF-8");
                    IOUtils.closeQuietly(sw);
                    fileOutputStream.close();
                }
            } catch (Exception e) {
                throw new RRException("渲染模板失败，表名：" + tableEntity.getTableName(), e);
            }
        }
    }

    /**
     * 生成mongo其他实体类的代码
     */
    public static void generatorMongoCode(String[] tableNames, ZipOutputStream zip) {
        for (String tableName : tableNames) {
            MongoDefinition info = MongoManager.getInfo(tableName);
            currentTableName = tableName;
            List<MongoGeneratorEntity> childrenInfo = info.getChildrenInfo(tableName);
            childrenInfo.remove(0);
            for (MongoGeneratorEntity mongoGeneratorEntity : childrenInfo) {
                generatorChildrenBeanCode(mongoGeneratorEntity, zip);
            }
        }
    }

    private static void generatorChildrenBeanCode(MongoGeneratorEntity mongoGeneratorEntity, ZipOutputStream zip) {
        //配置信息
        Configuration config = getConfig();
        boolean hasList = false;
        //表信息
        TableEntity tableEntity = mongoGeneratorEntity.toTableEntity();
        //表名转换成Java类名
        String className = tableToJava(tableEntity.getTableName(), config.getStringArray("tablePrefix"));
        tableEntity.setClassName(className);
        tableEntity.setClassname(StringUtils.uncapitalize(className));
        //列信息
        List<ColumnEntity> columsList = new ArrayList<>();
        for (Map<String, String> column : mongoGeneratorEntity.getColumns()) {
            ColumnEntity columnEntity = new ColumnEntity();
            String columnName = column.get("columnName");
            if (columnName.contains(".")) {
                columnName = columnName.substring(columnName.lastIndexOf(".") + 1);
            }
            columnEntity.setColumnName(columnName);
            columnEntity.setDataType(column.get("dataType"));
            columnEntity.setExtra(column.get("extra"));

            //列名转换成Java属性名
            String attrName = columnToJava(columnEntity.getColumnName());
            columnEntity.setAttrName(attrName);
            columnEntity.setAttrname(StringUtils.uncapitalize(attrName));

            //列的数据类型，转换成Java类型
            String attrType = config.getString(columnEntity.getDataType(), columnToJava(columnEntity.getDataType()));
            columnEntity.setAttrType(attrType);

            if (!hasList && "array".equals(columnEntity.getExtra())) {
                hasList = true;
            }
            columsList.add(columnEntity);
        }
        tableEntity.setColumns(columsList);

        //设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);
        String mainPath = config.getString("mainPath");
        mainPath = StringUtils.isBlank(mainPath) ? "io.renren" : mainPath;
        //封装模板数据
        Map<String, Object> map = new HashMap<>();
        map.put("tableName", tableEntity.getTableName());
        map.put("comments", tableEntity.getComments());
        map.put("pk", tableEntity.getPk());
        map.put("className", tableEntity.getClassName());
        map.put("classname", tableEntity.getClassname());
        map.put("pathName", tableEntity.getClassname().toLowerCase());
        map.put("columns", tableEntity.getColumns());
        map.put("hasList", hasList);
        map.put("mainPath", mainPath);
        map.put("package", config.getString("package"));
        map.put("moduleName", config.getString("moduleName"));
        map.put("author", config.getString("author"));
        map.put("email", config.getString("email"));
        map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN));
        VelocityContext context = new VelocityContext(map);

        //获取模板列表
//        List<String> templates = getMongoChildTemplates();
//        for (String template : templates) {
//            //渲染模板
//            StringWriter sw = new StringWriter();
//            Template tpl = Velocity.getTemplate(template, "UTF-8");
//            tpl.merge(context, sw);
//            try {
//                //添加到zip
//                zip.putNextEntry(new ZipEntry(getFilePath(template, tableEntity.getClassName(), config.getString("package"), config.getString("moduleName"))));
//                IOUtils.write(sw.toString(), zip, "UTF-8");
//                IOUtils.closeQuietly(sw);
//                zip.closeEntry();
//            } catch (IOException e) {
//                throw new RRException("渲染模板失败，表名：" + tableEntity.getTableName(), e);
//            }
//        }

    }

    /**
     * 列名转换成Java属性名
     */
    public static String columnToJava(String columnName) {
        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
    }

    /**
     * 表名转换成Java类名
     */
    public static String tableToJava(String tableName, String[] tablePrefixArray) {
        if (null != tablePrefixArray && tablePrefixArray.length > 0) {
            for (String tablePrefix : tablePrefixArray) {
                if (tableName.startsWith(tablePrefix)) {
                    tableName = tableName.replaceFirst(tablePrefix, "");
                }
            }
        }
        return columnToJava(tableName);
    }

    /**
     * 获取配置信息
     */
    public static Configuration getConfig() {
        try {
            return new PropertiesConfiguration("generator.properties");
        } catch (ConfigurationException e) {
            throw new RRException("获取配置文件失败，", e);
        }
    }

    /**
     * 获取文件名
     */
    public static String getFilePath(String template, String classNameNo, String classnameNo, String className, String packageName, String moduleName) {
        String packagePath = "main" + File.separator + "java" + File.separator;
        String frontPackagePath = "main" + File.separator + "java" + File.separator;
        Configuration config = getConfig();
        String path = config.getString("path");
        String apiPath = config.getString("apiPath");
        String frontPath = config.getString("frontPath");
        String frontModuleName = config.getString("frontModuleName");
        if (StringUtils.isNotBlank(packageName)) {
            packagePath += packageName.replace(".", File.separator) + File.separator + moduleName + File.separator;
            frontPackagePath += packageName.replace(".", File.separator) + File.separator + frontModuleName + File.separator +  moduleName + File.separator;
        }
        if (template.contains("Entity.java.vm")) {
            return path +  File.separator + packagePath + "model" + File.separator + className + ".java";
        }

        if (template.contains("Dao.xml.vm")) {
            return path +  File.separator +"main" + File.separator + "resources" + File.separator + "mapper" + File.separator + className + "Mapper.xml";
        }

        if (template.contains("Dao.java.vm")) {
            return path +  File.separator + packagePath + "dao" + File.separator + className + "Mapper.java";
        }

        if (template.contains("Service.java.vm")) {
            return path +  File.separator + packagePath + "service" + File.separator + classNameNo + "Service.java";
        }

        if (template.contains("EntityDto.java.vm")) {
            return path +  File.separator + packagePath + "dto" + File.separator + classNameNo + "Dto.java";
        }

        if (template.contains("ManagerImpl.java.vm")) {
            return path +  File.separator + packagePath + "manager" + File.separator + classNameNo + "ManagerImpl.java";
        }

        if (template.contains("Manager.java.vm")) {
            return apiPath +  File.separator + packagePath + "api" + File.separator + "manager" + File.separator + classNameNo + "Manager.java";
        }

        if (template.contains("EntityReq.java.vm")) {
            return apiPath +  File.separator + packagePath + "api" + File.separator + "req" + File.separator+ classnameNo + File.separator+ classNameNo + "Req.java";
        }

        if (template.contains("EntityResp.java.vm")) {
            return apiPath +  File.separator + packagePath + "api" + File.separator + "resp" + File.separator+ classNameNo + "Resp.java";
        }

        if (template.contains("EntityCreateReq.java.vm")) {
            return apiPath +  File.separator + packagePath + "api" + File.separator + "req" + File.separator+ classnameNo + File.separator+ classNameNo + "CreateReq.java";
        }

        if (template.contains("EntityUpdateReq.java.vm")) {
            return apiPath +  File.separator + packagePath + "api" + File.separator + "req" + File.separator+ classnameNo + File.separator+ classNameNo + "UpdateReq.java";
        }

        if (template.contains("Controller.java.vm")) {
            return frontPath + File.separator + frontPackagePath  + "controller" + File.separator + classNameNo + "Controller.java";
        }
        if (template.contains("Service_Portal.java.vm")) {
            return frontPath + File.separator + frontPackagePath + "service" + File.separator + classNameNo + "Service.java";
        }
        if (template.contains("EntityCreateReq_Portal.java.vm")) {
            return frontPath + File.separator + frontPackagePath  + "req" + File.separator + classnameNo + File.separator + classNameNo + "CreateReq.java";
        }
        if (template.contains("EntityResp_Portal.java.vm")) {
            return frontPath +  File.separator + frontPackagePath + "resp" + File.separator + classNameNo + "Resp.java";
        }
        if (template.contains("EntityUpdateReq_Portal.java.vm")) {
            return frontPath +  File.separator + frontPackagePath + "req" + File.separator + classnameNo + File.separator + classNameNo + "UpdateReq.java";
        }
        if (template.contains("EntityReq_Portal.java.vm")) {
            return frontPath +  File.separator + frontPackagePath + "req" + File.separator + classnameNo + File.separator + classNameNo + "Req.java";
        }
        if (template.contains("EntityDto_Portal.java.vm")) {
            return frontPath +  File.separator + frontPackagePath + "dto" + File.separator + classNameNo + "Dto.java";
        }
        if (template.contains("index.html.vm")) {
            return frontPath +  File.separator +"main" + File.separator + "resources" + File.separator + "templates" + File.separator + moduleName + File.separator+ classnameNo + File.separator + classnameNo + ".html";
        }

        if (template.contains("menu.sql.vm")) {
            return frontPath +  File.separator +"main"+ File.separator + StrUtil.toUnderlineCase(className) + "_menu.sql";
        }

        return null;
    }

    private static String splitInnerName(String name) {
        name = name.replaceAll("\\.", "_");
        return name;
    }
}
